-- 2022-8-15

---[[ 17.2 Lua语言中编写模块的基本方法

    -- 在Lua语言中创建模块最简单的方法是 创建一个表并将所有需要导出的函数放入其中 最后返回这个表 如下示例演示了这种方法

    -- 一个用于复数的简单模块
    do
        local M = {} --模块

        -- 创建一个新的复数
        local function new (r,l)
            return {r=r,i=i}
        end

        M.new = new -- 把'new'加到模块中

        -- constant 'i'
        M.i = new(0,1)

        function M.add(c1,c2)
            return new(c1.r + c2.r,c1.i + c2.i)
        end

        function M.sub(c1,c2)
            return new(c1.r - c2.r,c1.i - c2.i)
        end

        function M.mul(c1,c2)
            return new(c1.r * c2.r - c1.i * c2.i,c1.r * c2.i + c1.i * c2.r)
        end

        local function inv(c)
            local n = c.r^2 + c.i^2
            return new(c.r/n,-c.i/n)
        end

        function M.div(c1,c2)
            return M.mul(c1,inv(c2))
        end

        function M.tostring(c)
            return string.format("%g,%g",c.r,c.i)
        end

        return M
    end

    -- 请注意我们是如何通过简单地把new和inv声明为局部变量而使它们成为代码段的私有函数(private function)的
    -- 有些人不喜欢最后的返回语句 一种将其省略的方式是直接把模块对应的表放到pack-age.loaded中:
    local M = {}
    package.loaded[...] = M
      -- 跟之前一样但没有返回语句
    
    -- 请注意 函数require会把模块的名称作为第一个参数传给加载函数 因此 表索引中的可变长参数表达式...其实就是模块名
    -- 在这一赋值语句后 我们就不再需要在模块的最后返回M了:
    -- 如果一个模块没有返回值 那么函数require会返回package.loaded[modname]的当前值(如果不会nil的话)
    -- 不过 笔者认为在模块的最后加上return语句更清晰
    -- 如果 我们忘了return语句 那么在测试模块的时候很容易就会发现问题

    -- 另一种编写模块的方法是把所有的函数定义为局部变量 然后在最后构造返回的表 参见如下示例
    do
        -- 使用导出表的模块

        local function new(r,i)
            return {r=r,i=i}
        end

        -- 定义常量'i'
        local i = complex.new(0,1)

        -- 跟之前一样的其他函数
        return {
            new = new,
            i = i,
            add = add,
            sub = sub,
            mul = mul,
            div = div,
            tostring = tostring,
        }
    end

    -- 这种方式的有点在于 无须在每一个标识符前增加前缀M.或类似的东西
    -- 通过显式的导出表 我们能够以与在模块中相同的方式定义和使用导入的内部函数
    -- 这种方式的缺点在于 导出表位于模块最后而不是最前面(把前面的话当作简略文档的话更有用) 而且由于必须把每个名字都写两边
    -- 所以导出表有点冗余(这一缺点其实可能会变成优点 因为这允许函数在模块内和模块外具有不同的名称 不过程序很少会用到)

    -- 不管怎样 无论怎样定义模块 用户都能用标准的方法使用模块
    local cpx = require "complex"
    print(cpx.tostring(cpx.add(cpx.new(3,4),cpx.i)))  --> (3,5)
    -- 后续 我们会看到如何使用诸如元素和环境之类的高级Lua语言功能来编写模块
    -- 不过 除了发现由于失误而定义的全局变量时有一个技巧外 笔者在编写模块时都是用基本功能
--]]